package cn.bitkit.base.util;

import lombok.SneakyThrows;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 方法句柄工具类
 * @author changbo
 */
public class MethodHandleUtil {

    private static final Map<FieldKey, MethodHandle> fieldSetterCache = new ConcurrentHashMap<>();
    private static final Map<FieldKey, MethodHandle> fieldGetterCache = new ConcurrentHashMap<>();
    private static final Map<MethodKey, MethodHandle> methodCache = new ConcurrentHashMap<>();

    private record MethodKey(Class<?> clazz, String methodName, MethodType methodType) {
        @Override
        public int hashCode() {
            return clazz.hashCode() + methodName.hashCode() + methodType.hashCode();
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof MethodKey(Class<?> clazz1, String name, MethodType type)) {
                return clazz.equals(clazz1) && methodName.equals(name) && methodType.equals(type);
            }
            return false;
        }
    }
    private record FieldKey(Class<?> clazz, String fieldName) {
        @Override
        public int hashCode() {
            return clazz.hashCode() + fieldName.hashCode();
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof FieldKey(Class<?> clazz1, String name)) {
                return clazz.equals(clazz1) && fieldName.equals(name);
            }
            return false;
        }

    }

    public static MethodHandle getFieldSetter(Class<?> clazz, String fieldName) {
        FieldKey key = new FieldKey(clazz, fieldName);
        return fieldSetterCache.computeIfAbsent(key, k -> {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return MethodHandles.lookup().unreflectSetter(field);
            } catch (Exception e) {
                throw new RuntimeException("找不到字段: " + fieldName, e);
            }
        });
    }

    public static MethodHandle getFieldGetter(Class<?> clazz, String fieldName) {
        FieldKey key = new FieldKey(clazz, fieldName);
        return fieldGetterCache.computeIfAbsent(key, k -> {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return MethodHandles.lookup().unreflectGetter(field);
            } catch (Exception e) {
                throw new RuntimeException("找不到字段: " + fieldName, e);
            }
        });
    }

    public static MethodHandle getMethodHandle(Class<?> clazz, String methodName, MethodType methodType) {
        MethodKey key = new MethodKey(clazz, methodName, methodType);
        return methodCache.computeIfAbsent(key, k -> {
            try {
                return MethodHandles.lookup().findVirtual(clazz, methodName, methodType);
            } catch (Exception e) {
                throw new RuntimeException("找不到方法: " + methodName, e);
            }
        });
    }

    @SneakyThrows
    public static Object getValue(Object obj, String fieldName) {
        return MethodHandleUtil.getFieldGetter(obj.getClass(), fieldName).invoke(obj);
    }

    @SneakyThrows
    public static void setValue(Object obj, String fieldName, Object value) {
        MethodHandleUtil.getFieldSetter(obj.getClass(), fieldName).invoke(obj, value);
    }

}
